using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Apache.Cassandra;
using Cassandraemon.Builder;


namespace Cassandraemon.Operators
{


	class GetIndexedSlicesCassandraOperator<T> : CassandraOperatorBase<T>
	{
		public IndexClause IndexClause { get; set; }
		
		public ColumnParent ColumnParent { get; set; }
		
		public SlicePredicate Predicate { get; set; }
		
		public ConsistencyLevel ConsistencyLevel { get; set; }
	
	
		public GetIndexedSlicesCassandraOperator () { }
		
		
		protected override object OperateInner ()
		{
            var ctx = this.Context;
            var sw = Stopwatch.StartNew();
            List<KeySlice> keySliceList;
            try {
                keySliceList = Client.get_indexed_slices(ColumnParent, IndexClause, Predicate, ConsistencyLevel);
            } catch (Exception err) {
                ctx.OnExecuted(CassandraExecuteEventArgs.FromOperation(this, err, sw));
                throw;
            }
            ctx.OnExecuted(CassandraExecuteEventArgs.FromOperation(this, keySliceList, sw));

			var entityList = new List<CassandraEntity<T>>();
			
			foreach(KeySlice keySlice in keySliceList)
			{
				entityList.Add(MakeCassandraEntity(keySlice.Columns, keySlice.Key, this.ColumnParent));
			}
			
			return entityList;
		}

#if TRACE
        protected internal override void GetTraceData(List<object> data)
        {
            base.GetTraceData(data);
            data.Add("get_indexed_slices");
            var cp = this.ColumnParent;
            data.Add(cp.Column_family);
        }
#endif

		public static ICassandraOperator<T> Create(CassandraOperatorCondition<T> condition)
		{
			if(condition.IsSetKey) throw new InvalidOperationException("You can't specify Key in case of get_indexed_slices operation.");
			if(condition.IsSetKeys) throw new InvalidOperationException("You can't specify Keys in case of get_indexed_slices operation.");
			if(condition.IsSetFinishKey) throw new InvalidOperationException("You can't specify FinishKey in case of get_indexed_slices operation.");
			
			if(condition.IsSetColumns &&
			  (condition.IsSetStartColumn || 
			   condition.IsSetFinishColumn || 
			   condition.IsSetReverse ||
			   condition.IsSetCount))
				throw new InvalidOperationException("You can't specify both Columns and range column in case of get_indexed_slices operation.");
			
			
			var oper = new GetIndexedSlicesCassandraOperator<T>();
			
			oper.ConsistencyLevel = condition.ConsistencyLevel;
			oper.PostProcessor = condition.PostProcessor;
			
			oper.ColumnParent = new ColumnParent();
			oper.ColumnParent.Column_family = condition.ColumnFamily;
			oper.ColumnParent.Super_column = condition.SuperColumn;
			
			oper.Predicate = new SlicePredicate();
			oper.Predicate.Column_names = condition.Columns;
			if(condition.Columns == null)
			{
				oper.Predicate.Slice_range = new SliceRange();
				oper.Predicate.Slice_range.Start = condition.StartColumn;
				oper.Predicate.Slice_range.Finish = condition.FinishColumn;
				oper.Predicate.Slice_range.Reversed = condition.Reverse;
				oper.Predicate.Slice_range.Count = condition.Count;
			}
			
			
			oper.IndexClause = new IndexClause();
			oper.IndexClause.Start_key = condition.StartKey;
			oper.IndexClause.Count = condition.KeyCount;
			oper.IndexClause.Expressions = new List<IndexExpression>();
			
			CreateIndexExpression(oper, condition.EqIndexExpression, IndexOperator.EQ);
			CreateIndexExpression(oper, condition.GteIndexExpression, IndexOperator.GTE);
			CreateIndexExpression(oper, condition.GtIndexExpression, IndexOperator.GT);
			CreateIndexExpression(oper, condition.LteIndexExpression, IndexOperator.LTE);
			CreateIndexExpression(oper, condition.LtIndexExpression, IndexOperator.LT);
			
			
			Type type = typeof(T);
			
			if(type.Equals(typeof(Column)) || type.Equals(typeof(CounterColumn)))
		   	{
		   		// Set No Limit because of enable FirstColumn method etc.
			}
			else if(type.Equals(typeof(SuperColumn)) || type.Equals(typeof(CounterSuperColumn)))
			{
				if(condition.IsSetSuperColumn) throw new InvalidOperationException("You can't specify SuperColumn in case of get_indexed_slices SuperColumn.");
			}
			else if(type.Equals(typeof(List<Column>)) || type.Equals(typeof(List<CounterColumn>)))
			{
				// Do Nothing
			}
			else if(type.Equals(typeof(List<SuperColumn>)) || type.Equals(typeof(List<CounterSuperColumn>)))
			{
				if(condition.IsSetSuperColumn) throw new InvalidOperationException("You can't specify SuperColumn in case of get_indexed_slices SuperColumn.");
			}
			else
			{
				throw new InvalidOperationException("You must specify Column or SuperColumn or CounterColumn or CounterSuperColumn or List<Column> or List<SuperColumn> or List<CounterColumn> or List<CounterSuperColumn> to CassandraEntity's GenericType in get_indexed_slices operation."); 
			}
			
			return oper;
		}
		

		private static void CreateIndexExpression(GetIndexedSlicesCassandraOperator<T> oper, Dictionary<byte[], byte[]> dic, IndexOperator op)
		{
			foreach(var key in dic.Keys)
			{
				var expression = new IndexExpression();
				expression.Column_name = key;
				expression.Op = op;
				expression.Value = dic[key];
				
				oper.IndexClause.Expressions.Add(expression);
			}
		}
	}
}
